//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.DataLayer
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;
    using System.Text;
    using Er = Microsoft.Research.DataLayer;
    using System.Windows.Controls;

    /// <summary>
    /// 
    /// </summary>
    public class ExternalRegistryHelper : MarshalByRefObject
    {
        /// <summary>
        /// Regitry connection.
        /// </summary>
        private Connection registryConnection;

        /// <summary>
        /// Provider Instance.
        /// </summary>
        private IDataProvider providerInstance;

        /// <summary>
        /// Provider Type.
        /// </summary>
        private Type providerType;

        /// <summary>
        /// Registry data reference provider instance.
        /// </summary>
        private DataReferenceProvider provider;

        /// <summary>
        /// Temporaray directory path.
        /// </summary>
        private string tempPath;

        /// <summary>
        /// Gets the get current app domain.
        /// </summary>
        /// <value>The get current app domain.</value>
        public AppDomain GetCurrentAppDomain
        {
            get
            {
                return AppDomain.CurrentDomain;
            }
        }

        /// <summary>
        /// Gets the temporary path.
        /// </summary>
        /// <value>The temp path.</value>
        public string TempPath
        {
            get
            {
                return tempPath;
            }
        }

        /// <summary>
        /// Gets the parameters.
        /// </summary>
        /// <returns></returns>
        public string GetParameters()
        {
            IDataProviderConfiguratorWPF configurator = providerInstance.GetWPFConfigurator();
            return configurator.GetConfiguredParameters();
        }

        /// <summary>
        /// Sets the parameters.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public void SetParameters(string parameters)
        {
            IDataProviderConfiguratorWPF configurator = providerInstance.GetWPFConfigurator();
            configurator.SetParameters(parameters);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ExternalRegistryHelper"/> class.
        /// </summary>
        public ExternalRegistryHelper()
        {
        }

        /// <summary>
        /// Initializes the specified connection and provider.
        /// </summary>
        /// <param name="connection">The connection.</param>
        /// <param name="providerId">The provider id.</param>
        public void Initialize(Er.Connection connection, Guid providerId)
        {
            InitializeConnection(connection);

            InitializeDataProvider(providerId);

            InitializeTempPath();

            InitializeProvider();
        }

        /// <summary>
        /// Creates the instance.
        /// </summary>
        public void CreateInstance()
        {
            if (this.providerType != null)
            {
                ConstructorInfo consInfo = this.providerType.GetConstructor(new Type[] { });

                if (consInfo != null)
                {
                    this.providerInstance = consInfo.Invoke(null) as Er.IDataProvider;
                    this.providerInstance.Initialize();
                }
            }
        }

        /// <summary>
        /// Initializes the configurator.
        /// </summary>
        public void InitializeConfigurator()
        {
            if (this.providerInstance != null)
            {
                this.providerInstance.InitializeConfigurator();
            }
        }

        /// <summary>
        /// Retrieves the specified parameters.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public DataObject Retrieve(string parameters, string key)
        {
            return providerInstance.Retrieve(parameters, key);
        }

        /// <summary>
        /// Stores the specified parameters.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public string Store(string parameters, DataObject value)
        {
            return providerInstance.Store(parameters, value);
        }

        /// <summary>
        /// Stores the specified parameters.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public string Store(string parameters, string key, DataObject value)
        {
            return providerInstance.Store(parameters, key, value);
        }

        #region Private Methods
        /// <summary>
        /// Initializes the temp path.
        /// </summary>
        private void InitializeTempPath()
        {
            this.tempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "TridentTemp");
            this.tempPath = System.IO.Path.Combine(tempPath, Guid.NewGuid().ToString("N"));

            if (!Directory.Exists(tempPath))
            {
                Directory.CreateDirectory(tempPath);
            }
        }

        /// <summary>
        /// Initializes the data provider.
        /// </summary>
        /// <param name="providerID">The provider ID.</param>
        private void InitializeDataProvider(Guid providerID)
        {
            try
            {
                provider = Er.DataReferenceProvider.Load(providerID, this.registryConnection);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Initializes the connection.
        /// </summary>
        /// <param name="conn">The conn.</param>
        private void InitializeConnection(Er.Connection conn)
        {
            this.registryConnection = conn;

            SR_Connection.Init();
            this.registryConnection.Open();
            this.registryConnection.AutoSave = false;
        }

        /// <summary>
        /// This method loads the data reference provider assembly and then
        /// creates an instance of the provider control.
        /// </summary>
        /// <returns>The data reference provider</returns>
        private Er.IDataProvider InitializeProvider()
        {
            Er.IDataProvider dataProviderInit = null;
            if (this.provider != null)
            {
                try
                {
                    this.DownloadLibraries();

                    this.LoadDataProviderType();
                }
                catch (Exception exp)
                {
                    throw new DataProviderException(exp.Message, exp);
                }
            }

            return dataProviderInit;
        }

        /// <summary>
        /// Download the required dlls for the data reference provider.
        /// </summary>
        private void DownloadLibraries()
        {
            // Sort based on name
            this.provider.AssemblyPackages.GetEnumerator();
            this.provider.AssemblyPackages.Sort((p1, p2) => DateTime.Compare(p2.Updated.When, p1.Updated.When));

            if (this.provider.AssemblyPackages.Count > 0)
            {
                Er.AssemblyPackage package = this.provider.AssemblyPackages[0];

                // Download the libraries form the activity.
                foreach (Er.AssemblyImage img in package.AssemblyImages)
                {
                    string targetFile = Path.Combine(this.tempPath, img.Filename);

                    // if the file not exists in target folder and lib folder then download the file.
                    if (!File.Exists(targetFile) && img.Contents != null)
                    {
                        File.WriteAllBytes(targetFile, img.Contents);
                    }
                }
            }
        }

        /// <summary>
        /// Loads the data provider.
        /// </summary>
        private void LoadDataProviderType()
        {
            string[] assemblyFiles = Directory.GetFiles(this.tempPath, "*.dll");
            string errorDLLMsg = LoadAssemblies(assemblyFiles);

            assemblyFiles = Directory.GetFiles(tempPath, "*.exe");
            string errorExeMsg = LoadAssemblies(assemblyFiles);

            if (string.IsNullOrEmpty(errorDLLMsg + errorExeMsg))
            {
                Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
                foreach (Assembly asm in asms)
                {
                    this.providerType = asm.GetType(this.provider.ClassName, false, true);
                    if (this.providerType != null)
                    {
                        break;
                    }
                }
            }
            else
            {
                throw new ArgumentException(errorDLLMsg + errorExeMsg);
            }
        }

        /// <summary>
        /// Loads the assemblies from the files specified.
        /// </summary>
        /// <param name="assemblyFiles">The files which has to be loaded.</param>
        /// <returns></returns>
        private static string LoadAssemblies(string[] assemblyFiles)
        {
            StringBuilder errorLoadAssemblies = new StringBuilder();
            if (assemblyFiles != null)
            {
                foreach (string assemblyFile in assemblyFiles)
                {
                    try
                    {
                        LoadFrom(assemblyFile);
                    }
                    catch (ReflectionTypeLoadException rfNotFound)
                    {
                        StringBuilder errorString = new StringBuilder();
                        foreach (Exception exp in rfNotFound.LoaderExceptions)
                        {
                            errorString.AppendLine(exp.Message);
                        }

                        errorLoadAssemblies.Append(errorString.ToString());
                    }
                    catch (Exception exp)
                    {
                        errorLoadAssemblies.Append(exp.Message);
                    }
                }
            }

            return errorLoadAssemblies.ToString();
        }

        /// <summary>
        /// This function is used to load dll from the file.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        private static Assembly LoadFrom(string fileName)
        {
            Assembly depAsm = SearchInCurrentDomain(Path.GetFileNameWithoutExtension(fileName));
            if (depAsm == null)
            {
                depAsm = Assembly.LoadFrom(fileName);
            }

            return depAsm;
        }

        /// <summary>
        /// This function is used to get check whether the given
        /// assembly is presently loaded in the current appdomain.
        /// If assembly is loaded in the current appdoamin it returns the reference to that assembly.
        /// </summary>
        /// <param name="assemblyName">Assembly Name.</param>
        /// <returns>
        /// Reference to the assembly if present in the current appdomain.
        /// </returns>
        private static Assembly SearchInCurrentDomain(string assemblyName)
        {
            if (!string.IsNullOrEmpty(assemblyName))
            {
                Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
                for (int i = 0; i < loadedAssemblies.Length; i++)
                {
                    Assembly assembly = loadedAssemblies[i];

                    if ((assembly.FullName.StartsWith(assemblyName + " ", StringComparison.OrdinalIgnoreCase)) ||
                        (assembly.FullName.StartsWith(assemblyName + ",", StringComparison.OrdinalIgnoreCase)))
                    {
                        return assembly;
                    }
                }
            }

            return null;
        }
        #endregion

        /// <summary>
        /// Obtains a lifetime service object to control the lifetime policy for this instance.
        /// </summary>
        /// <returns>
        /// An object of type <see cref="T:System.Runtime.Remoting.Lifetime.ILease"/> used to control the lifetime policy for this instance. This is the current lifetime service object for this instance if one exists; otherwise, a new lifetime service object initialized to the value of the <see cref="P:System.Runtime.Remoting.Lifetime.LifetimeServices.LeaseManagerPollTime"/> property.
        /// </returns>
        /// <exception cref="T:System.Security.SecurityException">The immediate caller does not have infrastructure permission. </exception>
        /// <PermissionSet>
        /// 	<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="RemotingConfiguration, Infrastructure"/>
        /// </PermissionSet>
        public override object InitializeLifetimeService()
        {
            // Default lease time is five minutes. If no client accesses a object for more than five minutes after it is created, 
            // the .NET Remoting Framework will deactivate the object. 
            // When you design a object, overridding InitializeLifetimeService and returning a null object as the new lease, 
            // indicating that this lease never expires
            return null;
        }
    }
}
